package com.moonshine.utils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.moonshine.parser.models.ClassBaseSignature;
import com.moonshine.parser.models.ClassCollection;
import com.moonshine.parser.models.ClassSignature;
import com.moonshine.parser.models.MethodSignature;
import com.moonshine.parser.models.VariableSignature;

public class InfoDBUtils {

	static final Map<Caches, PreparedStatement> sCachedStatements = new HashMap<Caches, PreparedStatement>();

	public static enum Caches {
		CLASS_BY_PACKAGE_NAME, CLASS, MISSING_METHOD_VARIABLES, METHOD_VARIABLES, CLASS_METHOD, CLASS_BY_CHECKSUM, INSERT_CLASS, INSERT_CLASS_VARIABLE, INSERT_CLASS_VARIABLES, INSERT_METHOD_VARIABLE, INSERT_METHOD_VARIABLES, INSERT_CLASS_METHODS
	}

	public static List<MethodSignature> findMissingMethodSignatures(
			Connection conn, ClassSignature sig) {
		List<MethodSignature> list = new ArrayList<MethodSignature>();

		return list;
	}

	public static Map<MethodSignature, List<VariableSignature>> findMissingVariableSignatures(
			Connection conn, List<MethodSignature> missingMethodSigs) {
		// TODO Auto-generated method stub
		return null;
	}

	public static List<VariableSignature> findMissingVariableSignatures(
			Connection conn, ClassSignature sig) {
		List<VariableSignature> list = new ArrayList<VariableSignature>();

		return list;
	}

	/**
	 * Finds all missing variable signatures from a method Signature
	 * 
	 * @param conn
	 * @param sig
	 * @return
	 * @throws SQLException
	 */
	public static List<VariableSignature> findMissingVariableSignatures(
			Connection conn, MethodSignature methodSig) throws SQLException {
		List<VariableSignature> list = new ArrayList<VariableSignature>();
		Map<Integer, List<VariableSignature>> crossClassMapping = new HashMap<Integer, List<VariableSignature>>();
		ClassCollection classes = computeVariableClassCollection(conn,
				crossClassMapping, methodSig.paramaters);
		for (ClassCollection.Entry entry : classes) {
			crossClassMapping.remove(entry.checksum);
		}
		for (Integer checkSum : crossClassMapping.keySet()) {
			list.add(crossClassMapping.get(checkSum).get(0));
		}
		return list;
	}

	public static List<VariableSignature> findAllVariables(Connection conn,
			MethodSignature sig) throws SQLException {
		List<VariableSignature> list = new ArrayList<VariableSignature>();
		if (!sCachedStatements.containsKey(Caches.METHOD_VARIABLES)) {
			sCachedStatements
					.put(
							Caches.METHOD_VARIABLES,
							conn
									.prepareStatement("SELECT package_name,class_name,var_name,visibility"
											+ " 	FROM method_has_variables  "
											+ "	JOIN VARIABLES v ON(variable_id = variables.id) , "
											+ "	JOIN classes ON v.class_id = classes.id WHERE method_id = ?"));
		}

		final PreparedStatement stmt = sCachedStatements
				.get(Caches.METHOD_VARIABLES);

		stmt.setInt(0, getMethodID(conn, sig));
		ResultSet rs = stmt.executeQuery();
		VariableSignature varSig;
		String packageName;
		while (rs.next()) {
			varSig = new VariableSignature();
			varSig.access = rs.getInt("visibility");
			packageName = rs.getString("package_name");
			varSig.declaredType = (packageName.length() != 0) ? (packageName + ":")
					: "";
			varSig.declaredType = rs.getString("class_name");
			varSig.name = rs.getString("name");
			list.add(varSig);

		}
		rs.close();
		return list;
	}

	private static int getMethodID(Connection conn, MethodSignature methodSig)
			throws SQLException {
		// TODO Auto-generated method stub
		int methodID = -1;
		if (methodSig.classSig != null) {
			if (!sCachedStatements.containsKey(Caches.CLASS_METHOD)) {
				sCachedStatements
						.put(
								Caches.CLASS_METHOD,
								conn
										.prepareStatement("SELECT visibility,name,return_type,access FROM class_methods "
												+ "JOIN methods method_id = id"
												+ " WHERE class_id = ? AND name = ?"));
			}

			final PreparedStatement stmt = sCachedStatements
					.get(Caches.CLASS_METHOD);
		}

		return methodID;
	}

	public static int findClassID(Connection conn, ClassBaseSignature classSig)
			throws SQLException {

		return findClassID(conn, SignatureTools.getClassCRCValue(classSig));
	}

	public static int findClassID(Connection conn, int classCheckSum)
			throws SQLException {
		int classID = -1;
		if (!sCachedStatements.containsKey(Caches.CLASS_BY_CHECKSUM)) {

			sCachedStatements
					.put(
							Caches.CLASS_BY_CHECKSUM,
							conn
									.prepareStatement("SELECT id,package_name,class_name FROM classes WHERE checksum=?"));

		}
		final PreparedStatement stmt = sCachedStatements
				.get(Caches.CLASS_BY_CHECKSUM);

		stmt.setInt(1, classCheckSum);

		ResultSet rs = stmt.executeQuery();
		if (rs.next())
			classID = rs.getInt("id");
		rs.close();

		return classID;
	}

	public static int findClassID(Connection conn, String classQualifiedName)
			throws SQLException {
		int classID = -1;
		if (!sCachedStatements.containsKey(Caches.CLASS_BY_PACKAGE_NAME)) {

			sCachedStatements
					.put(
							Caches.CLASS_BY_PACKAGE_NAME,
							conn
									.prepareStatement("SELECT id,package_name,class_name FROM classes WHERE package_name=? AND class_name=?"));

		}
		final PreparedStatement stmt = sCachedStatements
				.get(Caches.CLASS_BY_PACKAGE_NAME);
		String[] names = classQualifiedName.split(":");

		stmt.setString(1, names.length == 2 ? names[0] : "");
		stmt.setString(2, names.length == 2 ? names[1] : names[0]);

		ResultSet rs = stmt.executeQuery();
		if (rs.next())
			classID = rs.getInt("id");

		return classID;
	}

	// public static List<ClassSignature>
	public static List<ClassSignature> findMissingClasses(Connection conn,
			List<VariableSignature> varSigs) throws SQLException {
		// TODO Auto-generated method stub

		Map<Integer, VariableSignature> mapping = new HashMap<Integer, VariableSignature>();

		for (VariableSignature varSig : varSigs) {

			if (varSig.declaredType != "*")
				mapping.put(SignatureTools
						.getClassCRCValue(varSig.declaredType), varSig);
		}

		ClassCollection collection = findClassesByChecksums(conn, mapping
				.keySet());

		for (ClassCollection.Entry entry : collection) {
			if (mapping.containsKey(entry.checksum))
				mapping.remove(entry.checksum);
		}

		List<ClassSignature> classSigs = new ArrayList<ClassSignature>();

		if (mapping.size() != 0) {// if any left
			ClassSignature classSig;
			for (VariableSignature varSig : mapping.values()) {
				String[] names = varSig.declaredType.split(":");
				classSig = new ClassSignature();
				if (names.length == 2) {// has package
					classSig.packageName = names[0];
					classSig.name = names[1];
				} else {
					classSig.name = names[0];
				}
				classSigs.add(classSig);
			}
		}

		return classSigs;
	}

	public static ClassCollection findClassesByChecksums(Connection conn,
			Collection collection) throws SQLException {
		// remove wildcard
		collection.remove(WILDCARD_CHECKSUM);
		ClassCollection classCol = new ClassCollection();
		Statement stmt = conn.createStatement();
		ResultSet rs = stmt
				.executeQuery("SELECT id,checksum,package_name,class_name FROM classes WHERE checksum IN("
						+ join(collection, ",") + ")");

		while (rs.next()) {
			classCol.add(rs.getInt("id"), rs.getString("package_name"), rs
					.getString("class_name"), rs.getInt("checksum"));
		}
		rs.close();
		stmt.close();

		return classCol;
	}

	public static String join(Collection s, String delimiter) {
		StringBuffer buffer = new StringBuffer();
		Iterator iter = s.iterator();
		while (iter.hasNext()) {
			buffer.append(iter.next());
			if (iter.hasNext()) {
				buffer.append(delimiter);
			}
		}
		return buffer.toString();
	}

	public static int insertClassSignature(Connection conn,
			ClassBaseSignature classSig) throws SQLException {
		// TODO Auto-generated method stub
		if (!sCachedStatements.containsKey(Caches.INSERT_CLASS)) {

			sCachedStatements
					.put(
							Caches.INSERT_CLASS,
							conn
									.prepareStatement(
											"INSERT INTO classes (package_name,class_name,checksum) VALUES(?,?,?)",
											Statement.RETURN_GENERATED_KEYS));

		}
		int classID = -1;
		final PreparedStatement stmt = sCachedStatements
				.get(Caches.INSERT_CLASS);

		stmt.setString(1, SignatureTools.getPackageName(classSig.packageName));
		stmt.setString(2, SignatureTools.getClassName(classSig.name));
		stmt.setLong(3, SignatureTools.getClassCRCValue(classSig));
		int rows = stmt.executeUpdate();
		// if () {
		ResultSet keys = stmt.getGeneratedKeys();
		if (keys.next()) {
			classID = keys.getInt(1);
		}
		keys.close();

		// }

		return classID;
	}

	final static int WILDCARD_CHECKSUM = SignatureTools.getClassCRCValue("*");

	private static ClassCollection computeVariableClassCollection(
			Connection conn,
			Map<Integer, List<VariableSignature>> crossClassMapping,
			List<VariableSignature> varSigs) throws SQLException {
		List<Integer> classCheckSums = new ArrayList<Integer>();
		int classCheckSum;

		List<VariableSignature> crossMappingVars;
		for (VariableSignature varSig : varSigs) {

			classCheckSum = SignatureTools
					.getClassCRCValue(varSig.declaredType);
			// check to see if cross mapping was found if not make a new
			// instance
			if (!crossClassMapping.containsKey(classCheckSum))
				crossClassMapping.put(classCheckSum,
						new ArrayList<VariableSignature>());
			// get intance and add varSig to collection
			crossMappingVars = crossClassMapping.get(classCheckSum);
			crossMappingVars.add(varSig);
			//
			if (classCheckSums.indexOf(classCheckSum) == -1)
				classCheckSums.add(classCheckSum);
		}
		final boolean hasWildCardVar = classCheckSums
				.contains(WILDCARD_CHECKSUM);
		ClassCollection classes = findClassesByChecksums(conn,
				(Collection) classCheckSums);

		if (hasWildCardVar)
			classes.add(0, "", "*", WILDCARD_CHECKSUM);
		return classes;
	}

	public static void insertMethodVariableSignatures(Connection conn,
			int methodID, int classID, List<VariableSignature> varSigs)
			throws SQLException {
		Map<Integer, List<VariableSignature>> crossClassMapping = new HashMap<Integer, List<VariableSignature>>();
		ClassCollection classes = computeVariableClassCollection(conn,
				crossClassMapping, varSigs);

		// (visibility,var_name,class_id)
		if (!sCachedStatements.containsKey(Caches.INSERT_METHOD_VARIABLE)) {
			sCachedStatements
					.put(
							Caches.INSERT_METHOD_VARIABLE,
							conn
									.prepareStatement(
											"INSERT INTO method_variables (visibility,var_name,class_id) VALUES(?,?,?)",
											Statement.RETURN_GENERATED_KEYS));
		}

		final PreparedStatement varStatement = sCachedStatements
				.get(Caches.INSERT_METHOD_VARIABLE);

		List<Integer> varIDs = new ArrayList<Integer>();
		List<VariableSignature> crossMappingVars;
		conn.setAutoCommit(false);
		ResultSet rs;

		for (ClassCollection.Entry entry : classes) {
			crossMappingVars = crossClassMapping.remove(entry.checksum);

			for (VariableSignature varSig : crossMappingVars) {

				varStatement.setInt(1, varSig.access);
				varStatement.setString(2, varSig.name);
				varStatement.setInt(3, entry.id);
				varStatement.execute();
				rs = varStatement.getGeneratedKeys();
				if (rs.next()) {
					varIDs.add(rs.getInt(1));
				}
				rs.close();

			}
		}

		conn.commit();

		/*
		 * if (inserts.size() != rows) {
		 * 
		 * // TODO : throw error }
		 */

		if (!sCachedStatements.containsKey(Caches.INSERT_METHOD_VARIABLES)) {
			sCachedStatements
					.put(
							Caches.INSERT_METHOD_VARIABLES,
							conn
									.prepareStatement("INSERT INTO method_has_variables (method_id,class_id,variable_id) VALUES(?,?,?)"));

		}
		PreparedStatement classVarStatement = sCachedStatements
				.get(Caches.INSERT_METHOD_VARIABLES);
		for (Integer varID : varIDs) {
			classVarStatement.setInt(1, methodID);
			classVarStatement.setInt(2, classID);
			classVarStatement.setInt(3, varID);
			classVarStatement.execute();
		}

		conn.commit();

		conn.setAutoCommit(true);

	}

	@SuppressWarnings("unchecked")
	public static void insertClassVariableSignatures(Connection conn,
			int classID, List<VariableSignature> varSigs) throws SQLException {
		// TODO Auto-generated method stub
		Map<Integer, List<VariableSignature>> crossClassMapping = new HashMap<Integer, List<VariableSignature>>();
		ClassCollection classes = computeVariableClassCollection(conn,
				crossClassMapping, varSigs);

		// (visibility,var_name,class_id)
		if (!sCachedStatements.containsKey(Caches.INSERT_CLASS_VARIABLE)) {
			sCachedStatements
					.put(
							Caches.INSERT_CLASS_VARIABLE,
							conn
									.prepareStatement(
											"INSERT INTO class_variables (visibility,var_name,class_id) VALUES(?,?,?)",
											Statement.RETURN_GENERATED_KEYS));
		}

		final PreparedStatement varStatement = sCachedStatements
				.get(Caches.INSERT_CLASS_VARIABLE);

		List<Integer> varIDs = new ArrayList<Integer>();
		List<VariableSignature> crossMappingVars;
		conn.setAutoCommit(false);
		ResultSet rs;
		for (ClassCollection.Entry entry : classes) {
			crossMappingVars = crossClassMapping.remove(entry.checksum);
			for (VariableSignature varSig : crossMappingVars) {

				varStatement.setInt(1, varSig.access);
				varStatement.setString(2, varSig.name);
				varStatement.setInt(3, entry.id);
				varStatement.execute();
				rs = varStatement.getGeneratedKeys();
				if (rs.next()) {
					varIDs.add(rs.getInt(1));
				}
				rs.close();

			}
		}

		conn.commit();

		/*
		 * if (inserts.size() != rows) {
		 * 
		 * // TODO : throw error }
		 */

		if (!sCachedStatements.containsKey(Caches.INSERT_CLASS_VARIABLES)) {
			sCachedStatements
					.put(
							Caches.INSERT_CLASS_VARIABLES,
							conn
									.prepareStatement("INSERT INTO class_has_variables (class_id,variable_id) VALUES(?,?)"));

		}
		PreparedStatement classVarStatement = sCachedStatements
				.get(Caches.INSERT_CLASS_VARIABLES);
		for (Integer varID : varIDs) {
			classVarStatement.setInt(1, classID);
			classVarStatement.setInt(2, varID);
			classVarStatement.execute();
		}

		conn.commit();
		conn.setAutoCommit(true);

	}

	public static void insertClassMethods(Connection conn, int classID,
			List<Integer> methodIDs) throws SQLException {
		// TODO Auto-generated method stub
		if (!sCachedStatements.containsKey(Caches.INSERT_CLASS_METHODS)) {
			sCachedStatements
					.put(
							Caches.INSERT_CLASS_METHODS,
							conn
									.prepareStatement(
											"INSERT INTO class_has_methods (class_id,method_id) VALUES(?,?)",
											Statement.RETURN_GENERATED_KEYS));
		}

		final PreparedStatement stmt = sCachedStatements
				.get(Caches.INSERT_CLASS_METHODS);

		conn.setAutoCommit(false);
		for (Integer methodID : methodIDs) {
			stmt.setInt(1, classID);
			stmt.setInt(2, methodID);
			stmt.execute();
		}
		conn.commit();
		conn.setAutoCommit(true);

	}
}
